package com.niiwoo.civet.user.service.local.portraitAuth;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.niiwoo.activity.event.constant.BizEventRabbitConstant;
import com.niiwoo.activity.event.dto.request.UserRealNameReqDTO;
import com.niiwoo.civet.user.constants.LoginAccountErrorCode;
import com.niiwoo.civet.user.dao.entity.*;
import com.niiwoo.civet.user.dao.mapper.*;
import com.niiwoo.civet.user.enums.*;
import com.niiwoo.civet.user.service.local.LoginAccountRedisService;
import com.niiwoo.civet.user.service.local.MobileIdentityService;
import com.niiwoo.civet.user.service.local.UserForbidService;
import com.niiwoo.civet.user.service.local.portraitAuth.dto.AuthCallInfoDTO;
import com.niiwoo.civet.user.service.local.portraitAuth.dto.AuthInfoDTO;
import com.niiwoo.civet.user.service.local.portraitAuth.dto.AuthResultDTO;
import com.niiwoo.tripod.im.component.IMService;
import com.niiwoo.tripod.im.request.UpdateUserInfoRequest;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Collections;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
public abstract class AbstractPortraitAuth implements PortraitAuthOperation, LoginAccountRedisService {

    @Autowired
    UserForbidService userForbidService;

    @Autowired
    DataStatusMapperExt dataStatusMapperExt;

    @Autowired
    UserImageFileMapperExt userImageFileMapperExt;

    @Autowired
    IdentityInfoMapperExt identityInfoMapperExt;

    @Autowired
    SnowflakeIdWorker snowflakeIdWorker;

    @Autowired
    RedisTemplate<String, String> redisTemplate;


    @Autowired
    UserAuthorizationInfoMapperExt userAuthorizationInfoMapperExt;

    @Autowired
    IdentityMappingMapper identityMappingMapper;

    @Autowired
    IMService imService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    private MobileIdentityService mobileIdentityService;

    /**
     * 是否禁止肖像认证
     * @param userId 用户ID
     * @return
     */
    private boolean isForBid(String userId) {
        return userForbidService.queryValidUserForbidInfo(userId, UserForbidTypeEnum.IDENTITY) != null;
    }

    /**
     * 是否已OCR
     * @param userId 用户ID
     * @return
     */
    private boolean hasOCRFinish(String userId) {
        return Optional.ofNullable(dataStatusMapperExt.getDataStatusByUserId(userId))
                        .orElse(Collections.emptyList())
                        .stream()
                        .anyMatch(s -> Objects.equals(s.getDataType(), UserDataTypeEnum.OCR.getValue()) &&  Objects.equals(s.getDataStatus(), UserDataStatusEnum.OCRStatus.YES_OCR.getStatus()) );
    }

    /**
     * 获取剩余次数
     * @param userId 用户ID
     * @return
     */
    protected int getRemainingTimes(String userId) {
        return Math.max(3 - this.getCacheValue(PORTRAIT_AUTH_ERROR_TIMES_FORMAT, new Object[]{userId}).map(Integer::valueOf).orElse(0), 0);
    }

    /**
     * 获取正面照
     * @param userId 用户ID
     * @return
     */
    private  UserImageFile getFrontImage(String userId) {
        return userImageFileMapperExt.selectBySubType(userId, UserImgTypeEnum.IDCARD_IMAGE.getValue(), UserImgTypeEnum.IDCARD_FRONT_IMAGE.getValue());
    }

    /**
     * 获取身份信息
     * @param userId 用户ID
     * @return
     */
    private IdentityInfo getIdentityInfo(String userId) {
        return identityInfoMapperExt.selectByPrimaryKey(userId);
    }

    @Override
    public RedisTemplate<String, String> getStringRedisTemplate() {
        return redisTemplate;
    }


    /**
     * 获取当天剩余秒数
     */
    private long getLeftSecondsToday() {
        LocalDate now = new LocalDate();
        return new Duration(now.toDateTimeAtCurrentTime(), now.plusDays(1).toDateTimeAtStartOfDay()).getStandardSeconds();
    }

    /**
     * 获取肖像认证状态记录
     * @return
     */
    protected DataStatus getPortraitAuthStatus(String userId, boolean isLock) {
        return isLock ? dataStatusMapperExt.selectByUserIdAndTypeForUpdate(userId, UserDataTypeEnum.PORTRAIT.getValue()) : dataStatusMapperExt.selectByUserIdAndType(userId, UserDataTypeEnum.PORTRAIT.getValue());
    }

    @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
    public <R> R doWithTransaction(Supplier<R> supplier) {
        return supplier.get();
    }

    /**
     * 保存肖像认证信息或进行肖像认证前的状态校验
     * @param userId 用户 ID
     */
    private void beforeSaveOrAuth(String userId) {
        // 剩余次数校验
        if (this.getRemainingTimes(userId) <= 0) {
            throw new BizException(LoginAccountErrorCode.OVER_PORTRAIT_AUTH_ERROR_TIMES);
        }
        // OCR状态校验
        if (!hasOCRFinish(userId)) {
            throw new BizException(LoginAccountErrorCode.NEED_OCR);
        }
        // 行为禁止校验
        if (isForBid(userId)) {
            throw new BizException(LoginAccountErrorCode.IDENTITY_AUTH_FORBID);
        }
    }

    @Override
    public <T extends AuthInfoDTO> boolean saveAuthInfo(T authInfo) {
        this.beforeSaveOrAuth(authInfo.getUserId());
        UserImageFile frontImage = this.getFrontImage(authInfo.getUserId());
        IdentityInfo identityInfo = this.getIdentityInfo(authInfo.getUserId());
        return ((AbstractPortraitAuth) AopContext.currentProxy()).doWithTransaction(() -> {
            // 肖像状态校验
            DataStatus portraitAuthStatus = AbstractPortraitAuth.this.getPortraitAuthStatus(authInfo.getUserId(), true);
            if (portraitAuthStatus != null) {
                UserDataStatusEnum.PortraitAuthStatus currentStatus = UserDataStatusEnum.PortraitAuthStatus.of(portraitAuthStatus.getDataStatus());
                if (currentStatus == null) {
                    throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
                }
                switch (currentStatus) {
                    case REVIEW:
                        throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_REVIEW);
                    case NEED_APPROVE:
                        throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_NEED_APPROVE);
                    case PASS_CERTIFIED:
                        throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_HAS_CERTIFIED);
                    case NOT_CERTIFIED:
                    case NOT_PASS: {
                        DataStatus updatePortraitAuthStatus = new DataStatus();
                        updatePortraitAuthStatus.setUserId(authInfo.getUserId());
                        updatePortraitAuthStatus.setDataType(UserDataTypeEnum.PORTRAIT.getValue());
                        updatePortraitAuthStatus.setDataStatus(UserDataStatusEnum.PortraitAuthStatus.REVIEW.getStatus());
                        dataStatusMapperExt.updateByUserIdAndType(updatePortraitAuthStatus);
                    }
                }
            } else {
                DataStatus initPortraitAuthStatus = new DataStatus();
                initPortraitAuthStatus.setId(snowflakeIdWorker.nextId());
                initPortraitAuthStatus.setUserId(authInfo.getUserId());
                initPortraitAuthStatus.setDataType(UserDataTypeEnum.PORTRAIT.getValue());
                initPortraitAuthStatus.setDataStatus(UserDataStatusEnum.PortraitAuthStatus.REVIEW.getStatus());
                initPortraitAuthStatus.setCreateTime(new Date());
                dataStatusMapperExt.insertSelective(initPortraitAuthStatus);
            }
            // 认证渠道信息
            UserAuthorizationInfo updateAuthorizationInfo = new UserAuthorizationInfo();
            updateAuthorizationInfo.setUserId(authInfo.getUserId());
            updateAuthorizationInfo.setFaceVerificationChannel(this.getSupportChannel().getValue());
            userAuthorizationInfoMapperExt.updateByPrimaryKeySelective(updateAuthorizationInfo);
            return AbstractPortraitAuth.this.innerSaveAuthInfo(authInfo, frontImage, identityInfo);
        });
    }

    /**
     * 子类保存肖像认证信息实现
     * @param f
     * @param frontImage
     * @param identityInfo
     * @return
     */
    abstract protected boolean innerSaveAuthInfo(AuthInfoDTO f, UserImageFile frontImage, IdentityInfo identityInfo);

    @Override
    public UserDataStatusEnum.PortraitAuthStatus auth(String userId) {
        this.beforeSaveOrAuth(userId);
        UserDataStatusEnum.PortraitAuthStatus currentStatusRead;
        DataStatus portraitAuthStatusRead = AbstractPortraitAuth.this.getPortraitAuthStatus(userId, false);
        if (portraitAuthStatusRead == null || (currentStatusRead = UserDataStatusEnum.PortraitAuthStatus.of(portraitAuthStatusRead.getDataStatus())) == null || currentStatusRead != UserDataStatusEnum.PortraitAuthStatus.REVIEW) {
            throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
        }
        IdentityInfo identityInfo = this.getIdentityInfo(userId);
        AuthCallInfoDTO authCallInfoDTO = this.innerCallAuth(userId, identityInfo);
        return ((AbstractPortraitAuth) AopContext.currentProxy()).doWithTransaction(() -> {
            // 状态校验
            DataStatus portraitAuthStatus = AbstractPortraitAuth.this.getPortraitAuthStatus(userId, true);
            UserDataStatusEnum.PortraitAuthStatus currentStatus = UserDataStatusEnum.PortraitAuthStatus.of(portraitAuthStatus.getDataStatus());
            if (currentStatus == null || currentStatus != UserDataStatusEnum.PortraitAuthStatus.REVIEW) {
                throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
            }
            UserDataStatusEnum.PortraitAuthStatus authStatus = AbstractPortraitAuth.this.innerPostAuth(userId, identityInfo, authCallInfoDTO);
            boolean isUpdateRealNameStatus = false;
            boolean isInsertIdentityMapping = false;
            // 肖像认证通过， 更新二要素状态
            if (authStatus == UserDataStatusEnum.PortraitAuthStatus.PASS_CERTIFIED) {
                DataStatus realNameStatus = dataStatusMapperExt.selectByUserIdAndType(userId, UserDataTypeEnum.TWO_ELEMENT.getValue());
                // 不存在则插入
                if (realNameStatus == null) {
                    DataStatus updateStatus = new DataStatus();
                    updateStatus.setId(snowflakeIdWorker.nextId());
                    updateStatus.setUserId(userId);
                    updateStatus.setDataType(UserDataTypeEnum.TWO_ELEMENT.getValue());
                    updateStatus.setDataStatus(UserDataStatusEnum.RealNameAuthStatus.PASS_CERTIFIED.getStatus());
                    updateStatus.setAuthTime(new Date());
                    updateStatus.setCreateTime(new Date());
                    isUpdateRealNameStatus = dataStatusMapperExt.insertSelective(updateStatus) > 0;
                    // 存在但不是已认证状态则更新
                } else if (!UserDataStatusEnum.RealNameAuthStatus.PASS_CERTIFIED.getStatus().equals(realNameStatus.getDataStatus())){
                    DataStatus updateStatus = new DataStatus();
                    updateStatus.setUserId(userId);
                    updateStatus.setDataType(UserDataTypeEnum.TWO_ELEMENT.getValue());
                    updateStatus.setDataStatus(UserDataStatusEnum.RealNameAuthStatus.PASS_CERTIFIED.getStatus());
                    updateStatus.setAuthTime(new Date());
                    isUpdateRealNameStatus = dataStatusMapperExt.updateByUserIdAndTypeIfPossible(updateStatus, UserDataStatusEnum.RealNameAuthStatus.PASS_CERTIFIED.getStatus()) > 0;
                }
                // 更新二要素成功，如果身份映射不存在，则插入
                if (isUpdateRealNameStatus && identityMappingMapper.selectByPrimaryKey(identityInfo.getIdCard()) == null) {
                    IdentityMapping identityMapping = new IdentityMapping();
                    identityMapping.setUserId(userId);
                    identityMapping.setIdCard(identityInfo.getIdCard());
                    identityMapping.setCreateTime(new Date());
                    isInsertIdentityMapping = identityMappingMapper.insertSelective(identityMapping) > 0;
                }
            }
            log.info("肖像认证###二要素更新###realName={}, identityMapping={}", isUpdateRealNameStatus, isInsertIdentityMapping);

            // 更新肖像认证状态
            DataStatus updateStatus = new DataStatus();
            updateStatus.setUserId(userId);
            updateStatus.setDataType(UserDataTypeEnum.PORTRAIT.getValue());
            updateStatus.setDataStatus(authStatus.getStatus());
            updateStatus.setAuthTime(new Date());
            dataStatusMapperExt.updateByUserIdAndType(updateStatus);
            log.info("肖像认证###肖像认证状态更新###authStatus={}", authStatus);
            mobileIdentityService.insertMappingData(userId);
            // 事务后置处理
            boolean reamNameStatus = isUpdateRealNameStatus;
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    super.afterCommit();
                    try {
                        // 二要素更新成功， 发送通知
                        if (reamNameStatus) {
                            // 标签时间 - 实名
                            UserRealNameReqDTO json = new UserRealNameReqDTO();
                            json.setUserId(userId);
                            json.setIdentityCard(identityInfo.getIdCard());
                            json.setRealNameDate(new Date());
                            json.setUpdateDate(new Date());
                            log.info("肖像认证成功###标签-实名认证, data={}", JSONObject.toJSONString(json));
                            rabbitTemplate.convertAndSend(BizEventRabbitConstant.Exchange.BIZ_EVENT_DIRECT_EXCHANGE, BizEventRabbitConstant.Key.BIZ_EVENT_USER_REAL_NAME_KEY, json);
                            // IM请求
                            UpdateUserInfoRequest request = new UpdateUserInfoRequest();
                            request.setGuid(userId);
                            request.setAddIdentity(1);
                            log.info("肖像认证成功###调用IM###请求， request={}", JSON.toJSONString(request));
                            CompletableFuture.runAsync(() -> imService.updateUserInfo(request));
                        }
                        // 认证不通过，增加错误次数
                        if (authStatus != UserDataStatusEnum.PortraitAuthStatus.PASS_CERTIFIED) {
                            AbstractPortraitAuth.this.incrCacheValue(PORTRAIT_AUTH_ERROR_TIMES_FORMAT, new Object[]{userId}, getLeftSecondsToday(), TimeUnit.SECONDS).map(Long::intValue).orElse(0);
                        }
                    } catch (Exception e) {
                        log.error(String.format("肖像认证###后置处理异常, message=%s", e.getMessage()), e);
                    }
                }
            });
            return authStatus;
        });
    }

    abstract protected AuthCallInfoDTO innerCallAuth(String userId, IdentityInfo identityInfo);

    /**
     * 子类肖像认证实现
     * @param userId 用户ID
     * @param identityInfo 身份信息
     * @return
     */
    abstract protected UserDataStatusEnum.PortraitAuthStatus innerPostAuth(String userId, IdentityInfo identityInfo, AuthCallInfoDTO authCallInfoDTO);

    @Override
    public boolean invalidateUnAuthInfo(String userId) {
        return ((AbstractPortraitAuth) AopContext.currentProxy()).doWithTransaction(() -> {
            // 状态校验
            DataStatus portraitAuthStatus = AbstractPortraitAuth.this.getPortraitAuthStatus(userId, true);
            if (portraitAuthStatus == null) {
                throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
            }
            UserDataStatusEnum.PortraitAuthStatus currentStatus = UserDataStatusEnum.PortraitAuthStatus.of(portraitAuthStatus.getDataStatus());
            if (currentStatus == null) {
                throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
            }
            if (currentStatus != UserDataStatusEnum.PortraitAuthStatus.REVIEW) {
                return false;
            }
            if (!AbstractPortraitAuth.this.innerInvalidateUnAuthInfo(userId)) {
                return false;
            }
            // 认证渠道信息
            UserAuthorizationInfo updateAuthorizationInfo = new UserAuthorizationInfo();
            updateAuthorizationInfo.setUserId(userId);
            updateAuthorizationInfo.setFaceVerificationChannel(PortraitChannelEnum.NOTHING.getValue());
            userAuthorizationInfoMapperExt.updateByPrimaryKeySelective(updateAuthorizationInfo);
            // 更新肖像认证状态
            DataStatus updateStatus = new DataStatus();
            updateStatus.setUserId(userId);
            updateStatus.setDataType(UserDataTypeEnum.PORTRAIT.getValue());
            updateStatus.setDataStatus(UserDataStatusEnum.PortraitAuthStatus.NOT_CERTIFIED.getStatus());
            updateStatus.setAuthTime(new Date());
            return dataStatusMapperExt.updateByUserIdAndType(updateStatus) > 0;
        });
    }

    /**
     * 子类肖像认证信息失效实现
     * @param userId
     * @return
     */
    abstract protected boolean innerInvalidateUnAuthInfo(String userId);

    @Override
    public <U extends AuthResultDTO> U getAuthResult(String userId, boolean isRecent, Class<U> cls) {
        DataStatus portraitAuthStatus;
        if (isRecent) {
            portraitAuthStatus = ((AbstractPortraitAuth) AopContext.currentProxy()).doWithTransaction(() -> this.getPortraitAuthStatus(userId, false));
        } else {
            portraitAuthStatus = this.getPortraitAuthStatus(userId, false);
        }
        if (portraitAuthStatus == null) {
            return null;
        }
        UserDataStatusEnum.PortraitAuthStatus status = UserDataStatusEnum.PortraitAuthStatus.of(portraitAuthStatus.getDataStatus());
        if (status == null) {
            throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_UNKNOWN);
        }
        U u = this.innerGetAuthResult(userId, status, isRecent, cls);
        u.setPortraitChannelEnum(this.getSupportChannel());
        return u;
    }

    abstract protected  <U extends AuthResultDTO> U innerGetAuthResult(String userId, UserDataStatusEnum.PortraitAuthStatus status, boolean isRecent, Class<U> cls);

}
